#pragma once
#include <iostream>
#include <sys/select.h>
#include "socket.hpp"
#include "log.hpp"
#include "Inetaddr.hpp"

using namespace socket_namespace;
using namespace log_namespace;

class selectServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    selectServer(uint16_t port)
        : _port(port), _listensock(std::make_unique<TCPSocket>())
    {
        _listensock->buildListenSocket(port);
    }

    void initServer()
    {
        for (int i = 0; i < gnum; i++)
        {
            fd_array[i] = gdefaultfd;
        }
        // 先把listensock添加到辅助数组中
        fd_array[0] = _listensock->getsockfd();
    }
    // 处理新的访问服务气器的套接字的方法
    void accepter()
    {
        // 我们叫做链接事件就绪，等同于读事件就绪(因为select只让第一个参数不为空)
        inetAddr addr;
        int ssocketfd = _listensock->Accepter(&addr);
        if (ssocketfd > 0)
        {
            int flag = 0;
            LOG(DEBUG, "get a new link, client info %s:%d\n", addr.IP().c_str(), addr.Port());
            for (int pos = 1; pos < gnum; pos++)
            {
                if (fd_array[pos] == gdefaultfd)
                {
                    flag = 1;
                    fd_array[pos] = ssocketfd;
                    LOG(INFO, "add %d to fd_array success!\n", ssocketfd);
                    break;
                }
            }
            if (flag == 0)
            {
                // 走到这，说明有太多文件描述符了，
                // 此时关闭监听套接字，就能安心处理这些已经在fd_array中的文件了                    //
                LOG(WARNING, "Server is Full!\n");
                ::close(ssocketfd);
            }
        }
    }

    // 处理一个普通的已就绪
    void handlerIO(int i)
    {
        char buffer[1024];
        ssize_t n = ::recv(fd_array[i], buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            // 以下只是测试代码，并不保证每次读取到的都是一个完整的报文
            buffer[n] = 0;
            std::cout << "client say: " << buffer << std::endl;
            std::string content = "<html><body><h1>hello bit</h1></body></html>";
            std::string echo_str = "HTTP/1.0 200 OK\r\n";
            echo_str += "Content-Type: text/html\r\n";
            echo_str += "Content-Length: ";
            echo_str += std::to_string(content.size()) + "\r\n\r\n";
            echo_str += content;
            // echo_str += buffer;
            // 如果是新的文件描述符，一般都是可以往这个文件描述符中直接写的
            ::send(fd_array[i], echo_str.c_str(), echo_str.size(), 0);
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit...\n");
            ::close(fd_array[i]);
            fd_array[i] = gdefaultfd;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            ::close(fd_array[i]);
            fd_array[i] = gdefaultfd;
        }
    }

    void handleEvent(fd_set &rfds)
    {
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
            {
                continue;
            }
            // 走到这，也就是遍历到了一个合法(大于等于0)的文件描述符
            if (FD_ISSET(fd_array[i], &rfds))
            {
                // 走到这，也就是找到了一个已经准备就绪的文件描述符
                if (_listensock->getsockfd() == fd_array[i])
                {
                    // 走到这，也就是遍历到了listen套接字
                    accepter();
                }
                else
                {
                    // 走到这，意味着找到了一个合法的，且就绪的，且不是listen套接字的一个普通文件描述符
                    //     那么就可以对这个文件进行正常处理
                    handlerIO(i);
                }
            }
        }
    }

        void loop()
        {
            while (true)
            {
                // 1. 设置fd_set类型中某个比特位的值只能用专门的位操作接口
                // 对文件描述符集进行初始化
                fd_set rfds;
                FD_ZERO(&rfds);
                int max_fd = -1;

                // 2. 将合法的fd添加到rfds集合中
                //    更新最大的max_fd
                for (int i = 0; i < gnum; i++)
                {
                    if (fd_array[i] == gdefaultfd)
                    {
                        continue;
                    }
                    FD_SET(fd_array[i], &rfds);
                    if (max_fd < fd_array[i])
                    {
                        max_fd = fd_array[i];
                    }
                }

                FD_SET(_listensock->getsockfd(), &rfds);

                // select处阻塞三秒，30秒内有文件描述符就绪，马上返回，而且将第一个数减少
                // 如果30秒内没有文件描述符就绪，到三秒以后就返回0
                struct timeval timeout = {30, 0};

                // select一直以非阻塞轮训的方式等待有没有文件描述符就绪
                // struct timeval timeout = {0, 0};

                // 不能直接accept
                // _listensock->Accepter();
                // 如果select的最后一个参数设置为 nullptr，则进入阻塞式等待
                int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
                switch (n)
                {
                case 0:
                    LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                    break;

                case -1:
                    LOG(ERROR, "select error\n");
                    break;

                default:
                    // LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                    LOG(INFO, "one of events has already!, n : %d\n", n);
                    handleEvent(rfds);
                    PrintDebug();
                    sleep(3);
                    break;
                }
            }
        }

    void PrintDebug()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] != gdefaultfd)
            {
                std::cout << fd_array[i] << " ";
            }
        }
        std::cout << "\n";
    }

    ~selectServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    int fd_array[gnum];
};
